import cv2
import numpy as np
from typing import List, Dict, Tuple, Optional
from pyzbar.pyzbar import decode as zbar_decode
import tempfile
import os


def _imdecode_from_bytes(image_bytes: bytes, flags: int = cv2.IMREAD_COLOR) -> np.ndarray:
    """
    函数说明：将图片字节数据解码为OpenCV图像矩阵。

    参数：
    - image_bytes: 图片的字节内容
    - flags: OpenCV解码标志（默认彩色）

    返回：
    - OpenCV图像矩阵（numpy.ndarray），失败时抛出异常
    """
    arr = np.frombuffer(image_bytes, dtype=np.uint8)
    img = cv2.imdecode(arr, flags)
    if img is None:
        raise ValueError("无法解析图片数据：cv2.imdecode返回None")
    return img


def get_image_dimensions(image_bytes: bytes) -> Tuple[int, int]:
    """
    函数说明：获取图片的宽度与高度信息。

    参数：
    - image_bytes: 图片的字节内容

    返回：
    - (width, height) 像素尺寸元组
    """
    img = _imdecode_from_bytes(image_bytes, flags=cv2.IMREAD_UNCHANGED)
    h, w = img.shape[:2]
    # 释放内存
    del img
    return w, h


def _decode_with_pyzbar(img: np.ndarray) -> List[Dict[str, str]]:
    """
    函数说明：使用pyzbar对OpenCV图像进行一维码解码。

    参数：
    - img: OpenCV图像矩阵

    返回：
    - 识别到的一维码列表，每项包含type与data
    """
    # 不指定symbols以避免不同平台缺失符号导致异常
    results = zbar_decode(img)
    items: List[Dict[str, str]] = []
    for r in results:
        try:
            data = r.data.decode("utf-8", errors="ignore")
        except Exception:
            data = r.data.decode("latin-1", errors="ignore")
        items.append({"type": r.type, "data": data})
    return items


def _decode_with_zxing(image_bytes: bytes) -> Optional[List[Dict[str, str]]]:
    """
    函数说明：使用ZXing进行一维码解码（需系统安装Java环境）。

    实现：
    - 将图片字节转PNG临时文件（提升兼容性）。
    - 调用Python zxing包装器进行解码。

    返回：
    - 成功时返回包含一个或多个条码的列表；失败或库不可用时返回None。
    """
    try:
        import zxing  # 动态导入，未安装则回退到pyzbar
    except Exception:
        return None

    # 重新编码为PNG以提高读取兼容性
    img = _imdecode_from_bytes(image_bytes, flags=cv2.IMREAD_COLOR)
    ok, encoded = cv2.imencode(".png", img)
    del img
    if not ok:
        return None

    tmp: Optional[str] = None
    try:
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tf:
            tf.write(encoded.tobytes())
            tmp = tf.name

        reader = zxing.BarCodeReader()
        # zxing Python包装通常返回单个结果
        res = reader.decode(tmp)
        if not res:
            return []
        items: List[Dict[str, str]] = []
        # 某些实现仅返回一个条码
        fmt = getattr(res, "format", "ZXING") or "ZXING"
        parsed = getattr(res, "parsed", None) or getattr(res, "raw", "")
        if parsed:
            items.append({"type": str(fmt), "data": str(parsed)})
        return items
    except Exception:
        return None
    finally:
        if tmp and os.path.exists(tmp):
            try:
                os.remove(tmp)
            except Exception:
                pass


def decode_barcodes(image_bytes: bytes) -> List[Dict[str, str]]:
    """
    函数说明：核心识别方法，读取图片字节并尝试识别一维码。

    策略：
    1) 优先使用ZXing进行识别（需Java），若不可用或失败则回退到pyzbar；
    2) pyzbar路径：原图 → 灰度 → 大图轻度下采样。

    参数：
    - image_bytes: 图片的字节内容

    返回：
    - 一维码字典列表，包含 type 与 data；若无结果返回空列表
    """
    # 先尝试ZXing
    zx_items = _decode_with_zxing(image_bytes)
    if zx_items is not None:
        # 返回None表示ZXing不可用或异常；空列表表示识别失败但库正常
        if zx_items:
            return zx_items

    # 使用pyzbar（原图）
    img_color = _imdecode_from_bytes(image_bytes, flags=cv2.IMREAD_COLOR)
    items = _decode_with_pyzbar(img_color)
    if items:
        del img_color
        return items

    # 大图下采样（宽或高大于2000像素时二分之一）
    img_gray = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
    items = _decode_with_pyzbar(img_gray)
    if items:
        del img_color, img_gray
        return items

    h, w = img_color.shape[:2]
    if max(w, h) > 2000:
        img_small = cv2.resize(img_color, (w // 2, h // 2), interpolation=cv2.INTER_AREA)
        items = _decode_with_pyzbar(img_small)
        del img_small

    # 释放内存
    del img_color, img_gray
    return items